package com.blank.shop.mysql;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

/**
 * <br/>Date 2021/9/12
 * <br/>Time 16:47:30
 *
 * @author _blank
 */
@Slf4j
@SpringBootApplication
public class MysqlApplication {
    /*
     *
     * MySQL日志  https://blog.csdn.net/lianzhouxiaowu/article/details/74438675
     * [玩转MySQL之一]MySQL体系架构简介  https://zhuanlan.zhihu.com/p/43736857
     * [玩转MySQL之二]MySQL连接机制浅析及运维  https://zhuanlan.zhihu.com/p/43941022
     * [玩转MySQL之三]MySQL用户及权限  https://zhuanlan.zhihu.com/p/55798418
     * [玩转MySQL之四]MySQL缓存机制  https://zhuanlan.zhihu.com/p/55947158
     * [玩转MySQL之五]SQL教程及练习题  https://zhuanlan.zhihu.com/p/56527835
     * [玩转MySQL之六]MySQL查询优化器  https://zhuanlan.zhihu.com/p/56790651
     * [玩转MySQL之七]InnoDB存储引擎架构简介  https://zhuanlan.zhihu.com/p/57172406
     * [玩转MySQL之八]MySQL日志分类及简介  https://zhuanlan.zhihu.com/p/58011817
     * [玩转MySQL之九]MySQL事务ACID详解  https://zhuanlan.zhihu.com/p/58534510
     * [玩转MySQL之十]InnoDB Buffer Pool详解  https://zhuanlan.zhihu.com/p/65811829
     * [玩转MySQL之十一]MySQL InnoDB文件存储结构  https://zhuanlan.zhihu.com/p/71204073
     *
     * 脏写：本质就是事务B去修改了事务A修改过的值，但是此时事务A还没提交，所以事务A随时会回滚，导致事务B修改的值也没了，这就是脏写的定义。
     * 脏读：本质其实就是事务B去查询了事务A修改过的数据，但是此时事务A还没提交，所以事务A随时会回滚导致事务B再次查询就读不到刚才事务A修改的数据了！
     *      脏读就是一个事务修改了一条数据的值，结果还没提交呢，另外一个事务就读到了你修改的值，然后你回滚了，人家事务再次读，就读不到了，也就是说人家事务读到了你修改之后还没提交的值，这就是脏读了。
     *
     * 如果你希望看到的场景就是不可重复读，也就是事务A在执行期间多次查询一条数据，每次都可以查到其他已经提交的事务修改过的值，那么就是不可重复读的。
     * 如果你希望的是，假设你事务A刚开始执行，第一次查询读到的是值A，然后后续你希望事务执行期间，读到的一直都是这个值A，不管其他事务如何更新这个值，哪怕他们都提交了，你就希望你读到的一直是第一次查询到的值A，那么你就是希望可重复读的
     *
     * 幻读：指的就是你一个事务用一样的SQL多次查询，结果每次查询都会发现查到了一些之前没看到过的数据。
     *
     *
     *
     * InnoDB默认是可重复读的（REPEATABLE READ）
     * 可以在my.inf文件的[mysqld]节里类似如下设置该选项：
     * transaction-isolation = {READ-UNCOMMITTED | READ-COMMITTED | REPEATABLE-READ | SERIALIZABLE}
     *
     * =================================================================================================================
     * 隔离级别                         脏读（Dirty Read）       不可重复读（NonRepeatable Read）     幻读（Phantom Read）
     * =================================================================================================================
     * 读未提交（Read uncommitted）      可能                    可能                                可能
     * 读已提交（Read committed）        不可能                  可能                                可能
     * 可重复读（Repeatable read）       不可能                  不可能                              可能 (貌似高版本mysql特殊解决了这个问题，做到不可能幻读)
     * 可串行化（Serializable ）         不可能                  不可能                              不可能
     *
     * Read uncommitted隔离级别，是不允许发生脏写的
     * Read committed隔离级别，不会发生脏写
     * Repeatable read隔离级别，不会发生脏写
     *
     *
     * 基于ReadView机制可以实现RC隔离级别，即你每次查询的时候都生成一个ReadView，这样的话，只要在你这次查询之前有别的事务提交了，那么别的事务更新的数据，你是可以看到的。
     *
     * MySQL中的多事务并发运行的隔离原理，其实这套隔离原理，说白了就是MVCC机制，也就是multi-version concurrent control，就是多版本并发控制机制，专门控制多个事务并发运行的时候，互相之间会如何影响。
     * 首先我们先要明白，多个事务并发运行的时候，同时读写一个数据，可能会出现脏写、脏读、不可重复读、幻读几个问题
     *
     * 所谓的脏写，就是两个事务都更新一个数据，结果有一个人回滚了把另外一个人更新的数据也回滚没了。
     * 脏读，就是一个事务读到了另外一个事务没提交的时候修改的数据，结果另外一个事务回滚了，下次读就读不到了。
     * 不可重复读，就是多次读一条数据，别的事务老是修改数据值还提交了，多次读到的值不同。
     * 幻读，就是范围查询，每次查到的数据不同，有时候别的事务插入了新的值，就会读到更多的数据。
     *
     * 针对这些问题，所以才有RU、RC、RR和串行四个隔离级别
     * RU隔离级别，就是可以读到人家没提交的事务修改的数据，只能避免脏写问题；
     * RC隔离级别，可以读到人家提交的事务修改过的数据，可以避免脏写和脏读问题。
     * RR是不会读到别的已经提交事务修改的数据，可以避免脏读、脏写和不可重复读的问题；
     * 串行是让事务都串行执行，可以避免所有问题。
     *
     * 然后MySQL实现MVCC机制的时候，是基于undo log多版本链条+ReadView机制来做的，
     * 默认的RR隔离级别，就是基于这套机制来实现的，依托这套机制实现了RR级别，除了避免脏写、脏读、不可重复读，还能避免幻读问题。
     * 因此一般来说我们都用默认的RR隔离级别就好了，这就是数据库的隔离机制以及底层的原理
     *
     *
     * 可以通过查询中的特殊语法加行锁，比如lock in share mode、for update等等，还可以通过一些方式在表级别去加锁。
     * 表锁，可以用如下语法来加：
     * LOCK TABLES xxx READ：这是加表级共享锁
     * LOCK TABLES xxx WRITE：这是加表级独占锁
     * DDL语句和增删改操作，确实是互斥的，大家要知道这一点。这是通过MySQL通用的元数据锁实现的，也就是Metadata Locks，但这还不是表锁的概念。
     * 因为表锁其实是InnoDB存储引擎的概念，InnoDB存储引擎提供了自己的表级锁，跟这里DDL语句用的元数据锁还不是一个概念。
     *
     *
     *
     *  数据库压测工具：https://github.com/akopytov/sysbench
     *
     * Prometheus其实就是一个监控数据采集和存储系统，
     *     他可以利用监控数据采集组件（比如mysql_exporter）从你指定的MySQL数据库中采集他需要的监控数据，
     *     然后他自己有一个时序数据库，他会把采集到的监控数据放入自己的时序数据库中，其实本质就是存储在磁盘文件里。
     *
     * Grafana就是一个可视化的监控数据展示系统，
     *     他可以把Prometheus采集到的大量的MySQL监控数据展示成各种精美的报表，让我们可以直观的看到MySQL的监控情况。
     *
     * 其实不光是对数据库监控可以采用Prometheus+Grafana的组合，
     * 对你开发出来的各种Java系统、中间件系统，都可以使用这套组合去进行可视化的监控，
     * 无非就是让Prometheus去采集你的监控数据，然后用Grafana展示成报表而已。
     *
     *
     *
     * java端    数据库连接池(请求)
     *     ↓                                                          ↑
     * mysql服务端    数据库连接池(接收)
     *     ↓获取SQL                                                          ↑
     * 线程
     *     ↓SQL语句                                                          ↑
     * SQL接口
     *     ↓解析SQL                                                          ↑
     * SQL解析器
     *     ↓最优查询路径                                                          ↑
     * 查询优化器
     *     ↓执行计划                                                          ↑
     * 执行器
     *     ↓调用接口完成SQL语句                                                          ↑
     * 存储引擎
     *     ↓访问数据                                                          ↑
     * 内存（存放数据）    或者    磁盘（存放数据）
     *
     *
     * 表当有id了，这个id是一个自增主键，默认就会基于他做一个聚簇索引。
     * 表可以针对多个字段名称建立一个联合索引。
     * 1、全值（等值）匹配规则  index(name)  select name from table where name = 'zhangsan'
     * 2、最左侧列匹配规则  index(name,age,address)  select name from table where name = 'zhangsan' and age = 18 and address like 'guangdongsheng%'
     * 3、最左前缀匹配规则 index(name,age,address)  select name from table where name = 'zhangsan' and age = 18
     * 4、范围查找规则
     * 5、等值匹配+范围匹配规则
     *
     * 运行select * from table order by xx1,xx2,xx3 limit 100这样的SQL语句：
     * 按照xx1,xx2,xx3三个字段来进行排序罢了，在联合索引的索引树里都排序好了，直接就按照索引树里的顺序，把xx1,xx2,xx3三个字段按照从小到大的值获取前面100条就可以了。
     * 但是这里有一些限定规则，因为联合索引里的字段值在索引树里都是从小到大依次排列的 ，
     * 所以你在order by里要不然就是每个字段后面什么都不加，直接就是order by xx1,xx2,xx3，要不然就都加DESC降序排列，就是order by xx1 DESC,xx2 DESC,xx3 DESC。
     * 不能order by语句里有的字段升序有的字段降序，那是不能用索引的。
     * order by语句里有的字段不在联合索引里，或者是你对order by语句里的字段用了复杂的函数，这些也不能使用索引去进行排序了。
     *
     * 通常而言，对于group by后的字段，最好也是按照联合索引里的最左侧的字段开始，按顺序排列开来，
     * 这样的话，其实就可以完美的运用上索引来直接提取一组一组的数据，然后针对每一组的数据执行聚合函数就可以了。
     * 其实大家会发现，这个group by和order by用上索引的原理和条件都是差不多的，
     * 本质都是在group by和order by之后的字段顺序和联合索引中的从最左侧开始的字段顺序一致，
     * 然后就可以充分利用索引树里已经完成排序的特性，快速的根据排序好的数据执行后续操作了。
     *
     * 回表：
     * 即使我们根据索引树按照条件找到了需要的数据，那也仅仅是索引里的几个字段的值和主键值，
     * 万一你搞了一个select *还需要很多其他的字段，那还得走一个回表操作，
     * 根据主键跑到主键的聚簇索引里去找，聚簇索引的叶子节点是数据页，找到数据页里才能把一行数据的所有字段值提取出来。
     *
     * 覆盖索引的概念，其实覆盖索引不是一种索引，他就是一种基于索引查询的方式罢了。
     *
     *
     * 设计索引原则：
     * 1、一般建立索引，尽量使用那些基数比较大的字段，就是值比较多的字段，那么才能发挥出B+树快速二分查找的优势来。
     * 2、尽量是对那些字段的类型比较小的列来设计索引，这种字段自己本身的值占用磁盘空间小，此时你在搜索的时候性能也会比较好一点。
     *     万一要是你真的有那种varchar(255)的字段，可能里面的值太大了，你觉得都放索引树里太占据磁盘空间了，可以换种策略，
     *     建立出来的索引其实类似于KEY my_index(name(20),age,course)，就这样的一个形式，假设name是varchar(255)类型的，但是在索引树里你对name的值仅仅提取前20个字符而已。
     *     但是假如你要是order by name，那么此时你的name因为在索引树里仅仅包含了前20个字符，所以这个排序是没法用上索引了！group by也是同理的。
     * 3、尽量不要让你的查询语句里的字段搞什么函数，或者是搞个计算，否则导致索引失效。
     * 4、一般大家设计索引别太多，建议两三个联合索引就应该覆盖掉你这个表的全部查询了。
     * 5、建议大家主键一定是自增的，别用UUID之类的，因为主键自增，那么起码你的聚簇索引不会频繁地分裂，主键值都是有序的，就会自然地新增一个页而已，但是如果你用的是UUID，那么也会导致聚簇索引频繁的页分裂。
     *
     * 技巧：
     * 1、实际设计索引的时候，必须把经常用做范围查询的字段放在联合索引的最后一个，才能保证你SQL里每个字段都能基于索引去查询。
     * 2、让最频繁查询的一些条件都放到索引里去，然后在查询的时候如果有些字段是不使用的，可以用in (所有枚举值)的方式去写，这样可以让所有查询条件都用上你的索引，同时对范围查询的XXX字段必须放在最后一个，这样保证范围查询也能用上索引。
     * 3、核心重点就是，尽量利用一两个复杂的多字段联合索引，抗下你80%以上的 查询，然后用一两个辅助索引抗下剩余20%的非典型查询，保证你99%以上的查询都能充分利用索引，就能保证你的查询速度和性能！
     *
     * 执行计划：
     * 高效：
     *     const          直接就可以通过聚簇索引或者二级索引+聚簇索引回源，轻松直接通过索引定位到数据，速度极快
     *     ref            字段是个普通二级索引，不是唯一索引，那么此时这种查询速度也是很快的
     *     ref_or_null    针对一个二级索引同时比较了一个值还有限定了IS NULL
     *     range          字段就是一个普通索引，此时就必然利用索引来进行范围筛选
     * 次一点：
     *     index          只要遍历二级索引就可以拿到你想要的数据，而不需要回源到聚簇索引的访问方式
     * 最差：
     *     all            就是直接全表扫描，扫描你的聚簇索引的所有叶子节点
     *
     *
     *
     * 了解一下MySQL里的成本是什么意思，简单来说，跑一个SQL语句，
     * 一般成本是两块，首先是那些数据如果在磁盘里，你要不要从磁盘里把数据读出来？这个从磁盘读数据到内存就是IO成本，
     * 而且MySQL里都是一页一页读的，读一页的成本的约定为1.0。
     * 成本值：一个数据页IO成本就是1.0，一条数据检测的CPU成本就是0.2。
     * 可以拿到你的表的统计信息：show table status like "表名"
     * rows就是表里的记录数，data_length就是表的聚簇索引的字节数大小，此时用data_length除以1024就是kb为单位的大小，然后再除以16kb（默认一页的大小），就是有多少页，此时知道数据页的数量和rows记录数，就可以计算全表扫描的成本了。
     * 全表扫描的成本计算方法：
     *     IO成本就是：数据页数量 * 1.0 + 微调值，CPU成本就是：行记录数 * 0.2 + 微调值，他们俩相加，就是一个总的成本值，比如你有数据页100个，记录数有2万条，此时总成本值大致就是100 + 4000 = 4100，在这个左右。
     * 使用一个索引进行查询的成本的计算方法：
     *     直接根据主键查，那就直接走一个聚簇索引就ok了，否则普通索引，一般都是两步走，先从二级索引查询一波数据，再根据这波数据的主键去聚簇索引回表查询。
     * 多表关联的成本估算以及执行计划选择方式，跟单表关联基本上是差不多的，只不过多表关联要多查几个表罢了。
     *
     *
     * explain : https://dev.mysql.com/doc/refman/5.7/en/explain-output.html
     *     id，就是说每个SELECT都会对应一个id;
     *     select_type，顾名思义，说的就是这一条执行计划对应的查询是个什么查询类型;
     *     table就是表名，意思是要查询哪个表;
     *     partitions，是表分区的概念;
     *     type，针对当前这个表的访问方法，比如说const、ref、range、index、all之类的，分别代表了使用聚簇索引、二级索引、全表扫描之类的访问方式;
     *     possible_keys，这也很关键，他是跟type结合起来的，意思就是说你type确定访问方式了，那么到底有哪些索引是可供选择，可以使用的呢，这都会放这里;
     *     key，就是在possible_keys里实际选择的那个索引;
     *     key_len，就是索引的长度
     *     ref，就是使用某个字段的索引进行等值匹配搜索的时候，跟索引列进行等值匹配的那个目标值的一些信息;
     *     rows，是预估通过索引或者别的方式访问这个表的时候，大概可能会读取多少条数据
     *     filtered，就是经过搜索条件过滤之后的剩余数据的百分比;
     *     extra是一些额外的信息，不是太重要
     *
     *
     * SQL查询优化技巧:
     * 在执行完SQL的EXPLAIN命令，看到执行计划之后，可以执行一下show warnings命令；
     *     执行SET optimizer_switch='semijoin=off'，也就是关闭掉半连接优化。或者。多种办法尝试去修改SQL语句的写法，在不影响他语义的情况下，尽可能的去改变SQL语句的结构和格式
     * 使用force index语法，强制让SQL语句使用了你指定的索引。
     * 慢查询本身不一定是SQL导致的，如果你觉得SQL不应该慢查询，结果他那个时间段跑这个SQL就是慢，此时你应该排查一下当时MySQL服务器的负载，尤其看看磁盘、网络以及CPU的负载，是否正常。
     * SQL调优利器profiling工具
     *     https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_profiling
     *     https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_profiling_history_size
     *
     *
     * 搭建主从复制，都是采取半同步的复制方式的，
     * 这个半同步的意思，就是说，你主库写入数据，日志进入binlog之后，
     * 起码得确保 binlog日志复制到从库了，你再告诉客户端说本次写入事务成功了。
     * 半同步复制配置：https://dev.mysql.com/doc/refman/5.7/en/replication-semisync.html
     * 一般来说主从复制都建议做成半同步复制，因为这样配合高可用切换机制，就可以保证数据库有一个在线的从库热备份主库的数据了，
     * 而且主要主库宕机，从库立马切换为主库，数据不丢失，数据库还高可用。
     * 主从延迟时间测试工具：主从之间到底延迟了多少时间呢？
     *     这个可以用一个工具来进行监控，比较推荐的是percona-toolkit工具集里的pt-heartbeat工具，
     *     https://github.com/percona/percona-toolkit
     *     他会在主库里创建一个heartbeat表，然后会有一个线程定时更新这个表里的时间戳字段，
     *     从库上就有一个monitor线程会负责检查从库同步过来的heartbeat表里的时间戳。
     *     把时间戳跟当前时间戳比较一下，其实就知道主从之间同步落后了多长时间了。
     * 如果说你要对MySQL做高可用保证数据绝对不丢失的话，建议还是用半同步机制比较好一些，同理最好是配合从库的并行复制机制。
     * MySQL 5.7就已经支持并行复制了，可以在从库里设置slave_parallel_workers>0，然后把slave_parallel_type设置为LOGICAL_CLOCK，就ok了。
     *
     * MySQL集群高可用
     * 一般生产环境里用于进行数据库高可用架构管理的工具是MHA，也就是Master High Availability Manager and Tools for MySQL，是日本人写的，
     * Development tree of Master High Availability Manager and tools for MySQL (MHA), Manager part    https://github.com/yoshinorim/mha4mysql-manager
     * Development tree of Master High Availability Manager and tools for MySQL (MHA), Node (MySQL Server) part    https://github.com/yoshinorim/mha4mysql-node
     * 用perl脚本写的一个工具，这个工具就是专门用于监控主库的状态，如果感觉不对劲，可以把从库切换为主库。
     *
     * 分库分表
     *
     */

    public static void main(String[] args) {
        final ConfigurableApplicationContext run = SpringApplication.run(MysqlApplication.class, args);

        // do you business
        log.info("hello, mysql!");

        run.stop();
        log.info("exit code: {}", SpringApplication.exit(run));
    }

}
